{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Bokeh Demo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "https://bokeh.pydata.org/en/latest/"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Examples: https://bokeh.pydata.org/en/latest/docs/gallery.html"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## What is Bokeh\n",
    "\n",
    "Bokeh is an interactive visualization library that targets modern web browsers for presentation. It is good for:\n",
    "\n",
    "* Interactive visualization in modern browsers\n",
    "* Standalone HTML documents, or server-backed apps\n",
    "* Expressive and versatile graphics\n",
    "* Large, dynamic or streaming data\n",
    "* Easy usage from python (or Scala, or R, or...)\n",
    "\n",
    "And most importantly:\n",
    "\n",
    "## <center><i>NO JAVASCRIPT REQUIRED</i></center>\n",
    "\n",
    "The goal of Bokeh is to provide elegant, concise construction of novel graphics in the style of D3.js, from the comfort of high level languages such as Python, and to extend this capability with high-performance interactivity over very large or streaming datasets. Bokeh can help anyone who would like to quickly and easily create interactive plots, dashboards, and data applications."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### The basic steps to creating plots with the bokeh.plotting interface are:\n",
    "\n",
    " * Prepare some data\n",
    "  * Can be plain python lists, NumPy arrays, or Pandas series.\n",
    " * Tell Bokeh where to generate output\n",
    "  * can use output_file() or output_notebook() for use in Jupyter notebooks.\n",
    " * Call figure()\n",
    "  * This creates a plot with typical default options and easy customization of title, tools, and axes labels.\n",
    "  * Here, we use GMapPlot\n",
    " * Add renderers\n",
    "  * In this case, we use source() for our data, specifying visual customizations like colors, legends and widths.\n",
    "  * We also add other features like a slider, hovertips, and a dropdown.\n",
    " * Ask Bokeh to show() or save() the results.\n",
    "  * These functions save the plot to an HTML file and optionally display it - with interactivity -  in a browser."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Bokeh can also be used to create map-based visualizations. In this tutorial we will use the Google Maps API to visualize our data on top of Google Maps, using bokeh's GMapPlot."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "First things first: we're going to need a Google Maps Developer key. Sign up for one here: https://developers.google.com/maps/documentation/javascript/get-api-key"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Save your API key somewhere secure - you're going to need it soon."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, we import our dataset into a pandas dataframe. Since we already did some data exploration in the previous tutorial, we'll get right to creating the visualization."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "os.environ['BOKEH_RESOURCES'] = 'inline'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data = pd.read_csv('./data/metadata.csv') "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data.info()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# we want an actual datetime \n",
    "data['Comm Timedate String'] = pd.to_datetime(data['Comm Timedate String'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# now we'll grab the hour as separate column\n",
    "data['hour'] = data['Comm Timedate String'].apply(lambda x: x.hour)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# for our map, we'll select just a few days in our dataset to make it more manageable\n",
    "data = data[(data['Comm Timedate String'] > '9/23/14 00:00') & (data['Comm Timedate String'] < '9/26/14 00:00')]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# create a date column\n",
    "data['date'] = data['Comm Timedate String'].dt.strftime('%d')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# what did we end up with?\n",
    "data.info()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Looks good, let's start making the visualization!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Install bokeh and import packages: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# we only have to install bokeh once\n",
    "!pip install bokeh"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# a bunch of packages\n",
    "from bokeh.io import output_file, show, save, curdoc\n",
    "from bokeh.models import (\n",
    "  GMapPlot, GMapOptions, ColumnDataSource, Circle, Range1d, PanTool, \n",
    "    WheelZoomTool, BoxSelectTool, LinearColorMapper, CategoricalColorMapper, HoverTool,\n",
    "    Plot, Circle, LinearAxis, Text,\n",
    "    SingleIntervalTicker, Slider, CustomJS, Select, Slider\n",
    ")\n",
    "from bokeh.palettes import Spectral6\n",
    "from bokeh.layouts import column, row, widgetbox\n",
    "from bokeh.models.widgets import Slider, Select"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# set the data source - we'll pick certain columns from the dataframe that we want to visualize on a map\n",
    "source = ColumnDataSource(data=\n",
    "    {'long' : data['Longitude'],\n",
    "    'lat'  : data['Latitude'],\n",
    "    'loc': data['Cell Tower Location'],\n",
    "    'type': data['Comm Type'],\n",
    "    'hour': data['hour'],\n",
    "    'date': data['date']\n",
    "    })"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# set map options - we're going to cheat and use the lat and long for Sydney, AU since we already know where it is\n",
    "# this set's the coordinates for the center of the map\n",
    "map_options = GMapOptions(lat=-33.865, lng=151.216, map_type=\"roadmap\", zoom=12) \n",
    "\n",
    "# initiate our plot with the map options we just defined using GMapPlot\n",
    "plot = GMapPlot(x_range=Range1d(), y_range=Range1d(), \n",
    "                map_options=map_options, plot_width=600, plot_height=700)\n",
    "\n",
    "plot.title.text = \"cell tower data\"\n",
    "\n",
    "# FILL IN YOUR API KEY HERE! \n",
    "plot.api_key = 'pasteYourAPIkeyHere'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "show(plot)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Let's add some data points and tools!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# map colors of the circles to comm type\n",
    "mapper = CategoricalColorMapper(\n",
    "    palette=Spectral6[::-1], # reverse the order of the color palette (so colors show up well on map)\n",
    "    factors=list(set(data['Comm Type']))\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# define circles for location points on the map\n",
    "circle = Circle(x=\"long\", y=\"lat\", \n",
    "                fill_color={'field': 'type', 'transform': mapper}, \n",
    "                fill_alpha=1, line_color=None, size=14)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# set hover tips - text to appear when the mouse hovers over a point\n",
    "hover = HoverTool(tooltips=[(\"date\", '@date'), \n",
    "                    (\"location\", '@loc')],\n",
    "                    )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# add tools to the plot\n",
    "plot.add_tools(PanTool(), WheelZoomTool(), BoxSelectTool(), hover)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# add circles and source to plot\n",
    "plot.add_glyph(source, circle)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# what does it look like now?\n",
    "show(plot)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Okay, we have our basic map plot! The colors of the circles correspond to comm type and the hover tips display the date and location.\n",
    "(Notice they show multiple values since it's mapping to every row in the dataframe - not ideal but we'll skip over it for now.)\n",
    "#### Now let's add some tools to interactively filter the data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Make a slider object for the hour\n",
    "slider = Slider(start=0, end=23, value=0, step=1, title=\"Time of day\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define the callback function - what happens when the slider changes\n",
    "# the plot will only show the data where the hour column matches the hour we select on the slider\n",
    "\n",
    "def callback(attr, old, new):\n",
    "    new_h = slider.value # get the value for hour from the slider\n",
    "    \n",
    "    # set the source data to the original dataset filtered by the new hour\n",
    "    source_data = {\n",
    "             'long'  : data.loc[data['hour']==new_h].long, \n",
    "             'lat'   : data.loc[data['hour']==new_h].lat,\n",
    "             'loc': data.loc[data['hour']==new_h].loc,\n",
    "             'type': data.loc[data['hour']==new_h].type,\n",
    "            'hour': data.loc[data['hour']==new_h].hour,\n",
    "             'date': data.loc[data['hour']==new_h].date\n",
    "    }"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Attach the callback to the 'value' property of slider\n",
    "slider.on_change('value', callback)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Make a layout of slider and plot and add it to the current document\n",
    "layout = column(plot, slider)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "show(column(plot, widgetbox(slider)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# make a dropdown object for the date\n",
    "select = Select(\n",
    "    options=['23', '24', '25'], \n",
    "    value='23', title=\"Date\") #sets the default value"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# define the callback for the dropdown \n",
    "def callback_menu(attr, old, new):\n",
    "    if new == '23': \n",
    "        source.data = {\n",
    "             'long'  : data.loc[data['date']==23].long,\n",
    "             'lat'   : data.loc[data['date']==23].lat,\n",
    "             'loc': data.loc[data['date']==23].loc,\n",
    "            'type': data.loc[data['date']==23].type,\n",
    "            'hour': data.loc[data['date']==23].hour,\n",
    "             'date': data.loc[['date']==23].date\n",
    "        }\n",
    "   \n",
    "    elif new == '24':\n",
    "        source.data = {\n",
    "             'long'  : data.loc[data['date']==24].long,\n",
    "             'lat'   : data.loc[data['date']==24].lat,\n",
    "             'loc': data.loc[data['date']==24].loc,\n",
    "            'type': data.loc[data['date']==24].type,\n",
    "            'hour': data.loc[data['date']==24].hour,\n",
    "             'date': data.loc[['date']==24].date\n",
    "        }\n",
    "        \n",
    "    elif new == '25':\n",
    "        source.data = {\n",
    "             'long'  : data.loc[data['date']==25].long,\n",
    "             'lat'   : data.loc[data['date']==25].lat,\n",
    "             'loc': data.loc[data['date']==25].loc,\n",
    "            'type': data.loc[data['date']==25].type,\n",
    "            'hour': data.loc[data['date']==25].hour,\n",
    "             'date': data.loc[['date']==25].date\n",
    "        }"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "select.on_change('value', callback_menu)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# show plot\n",
    "show(column(plot, widgetbox(slider, select)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "#### Looking good - we have our map, our slider, and our dropdown. \n",
    " *But why isn't anything changing when we move the slider or change the dropdown?*\n",
    "#### This is because the bokeh is designed to run Python scripts on the browser - we have to use the Bokeh Server to tap into the full interactive capabilities."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "From the docs:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The architecture of Bokeh is such that high-level “model objects” (representing things like plots, ranges, axes, glyphs, etc.) are created in Python, and then converted to a JSON format that is consumed by the client library, BokehJS. Using the Bokeh Server, it is possible to keep the “model objects” in python and in the browser in sync with one another, creating powerful capabilities:\n",
    "\n",
    "* respond to UI and tool events generated in a browser with computations or queries using the full power of python\n",
    "* automatically push updates the UI (i.e. widgets or plots), in a browser\n",
    "* use periodic, timeout, and asychronous callbacks drive streaming updates\n",
    "\n",
    "***This capability to synchronize between python and the browser is the main purpose of the Bokeh Server.***"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In order to do this, we would need to finalize the visualization we've created and save it as a python script (here as 'myapp.py'), with any necessary data in the same directory, and then serve it on the bokeh server using the following command:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "```bokeh serve --show myapp.py```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### That's outside the scope of this demo, but you can dive into much, much more depth yourself!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Read the docs: https://hub.mybinder.org/user/bokeh-bokeh-notebooks-yefwfcas/notebooks/tutorial/11%20-%20Running%20Bokeh%20Applictions.ipynb"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
